package com.ydj;

import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.lang.management.ManagementFactory;
import java.nio.channels.FileChannel;
import java.nio.channels.FileLock;
import java.text.SimpleDateFormat;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Date;

import javax.annotation.PostConstruct;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

import com.ydj.constants.PingPongConstant;
import com.ydj.entity.PingPongEntity;
import com.ydj.repository.PingPongEntityRepository;

import cn.hutool.http.HttpRequest;
import cn.hutool.http.HttpResponse;
import cn.hutool.http.HttpStatus;
import cn.hutool.json.JSONObject;
import lombok.extern.slf4j.Slf4j;

@Component
@Slf4j
public class PingComponent {
    @Value(value = "${lockFilePath}")
    private String lockFilePath;

    @Value(value = "${logFilePath}")
    private String logFilePath;
    @Value(value = "${pongServerHost}")
    private String pongServerHost;

    private Integer maxProcess = 2;
    // 锁2个字符，用于保存秒数
    private Integer lockLength = 2;

    private String processId;
    private Integer lockIndex = 0;
    @Autowired
    public PingPongEntityRepository pingpongRepository;

//    private static SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");

    @PostConstruct
    private void getProcessId() {
        String processName = ManagementFactory.getRuntimeMXBean().getName();
        processId = processName.split("@")[0];
        System.out.println("系统启动进程ID：" + processId);
        // 创建目录
        File lockFile = new File(lockFilePath);
        lockFile.getParentFile().mkdirs();
        File logFile = new File(logFilePath);
        logFile.mkdirs();
        //

    }

    @Scheduled(cron = "*/1 * * * *  ?")
//    @Scheduled(initialDelay =2000, fixedRate = 1000)
    public PingPongEntity sendPing() {
//        Date now=new Date();
        String curSecond = LocalDateTime.now().format(DateTimeFormatter.ofPattern("ss"));
        PingPongEntity entity = new PingPongEntity();
        entity.setSendDateTime(LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS")));
        entity.setProcessId(processId);
        
        log.info("lockFilePath={}", lockFilePath);
        try (RandomAccessFile file = new RandomAccessFile(lockFilePath, "rw");) {
            try {
                int index = lockIndex;
                while (index < maxProcess * lockLength) {
                    Boolean lockedResult = getFileLock(file, index,curSecond);
                    if (!lockedResult) {
                        log.error("fileLock index fail:{}", index);
                        index = index + lockLength;
                        continue;
                    }
                    // send message
                    HttpRequest request = new HttpRequest("http://" + pongServerHost + "/pong/say?say=Hello");
                    HttpResponse response = request.execute();
                    if (response.getStatus() == HttpStatus.HTTP_OK) {
                        entity.setStatus(PingPongConstant.STATUS_RESP_OK);
                        entity.setResponseMsg(response.body());
                    } else {
                        entity.setStatus(PingPongConstant.STATUS_RESP_429);
                        entity.setResponseMsg(response.getStatus() + "");
                    }
                    entity.setResponseDateTime(LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS")));
                    break;
                }

                if (entity.getStatus() == null) {
                    log.info("rate limited");
                    entity.setStatus(PingPongConstant.STATUS_RATE_LIMITED);
                    entity.setResponseMsg("rateLimit");
                }
            } catch (IOException e) {
                e.printStackTrace();
                throw e;
            }
        } catch (Exception e) {
            e.printStackTrace();
            entity.setStatus(PingPongConstant.STATUS_RESP_OTHER);
            entity.setResponseMsg(e.getMessage());
            log.error(entity.toString());
        } finally {
            savelogFile(entity);
            pingpongRepository.insert(entity);
        }
        return entity;
    }

    private Boolean getFileLock(RandomAccessFile file, int index,String curSecond) throws IOException {
        log.info("try lock index={},curSecond={}", index, curSecond);
        FileChannel fileChannel = file.getChannel();
        try(FileLock fileLock = fileChannel.tryLock(index, lockLength, false)){
            byte[] b = new byte[2];
            if (fileLock == null) {
                return false;
            } else {
                log.info("seek index:{}", index);
                file.seek(index);
                int readed = file.read(b, 0, lockLength);
                String fileSecond = new String(b, "utf-8");
                log.info("fileSecond={}", fileSecond);
                if (readed != 0 && curSecond.equals(fileSecond)) {
                    return false;
                }
                file.seek(index);
                file.write(curSecond.getBytes());
                log.info("use fileLock index:" + index);
                return true;
            }
        }
    }

    /**
     * 保存文件日志，可以考虑使用 log 来保存
     * 
     * @param entity
     */
    private void savelogFile(PingPongEntity entity) {
        log.debug(new JSONObject(entity).toJSONString(0));
    }

    public String getLockFilePath() {
        return lockFilePath;
    }

    public void setLockFilePath(String lockFilePath) {
        this.lockFilePath = lockFilePath;
    }

    public Integer getMaxProcess() {
        return maxProcess;
    }

    public void setMaxProcess(Integer maxProcess) {
        this.maxProcess = maxProcess;
    }

    public Integer getLockIndex() {
        return lockIndex;
    }

    public void setLockIndex(Integer lockIndex) {
        this.lockIndex = lockIndex;
    }

    public void setProcessId(String processId) {
        this.processId = processId;
    }

}
